package com.gitee.zhziyun.cnfsystem.utils;

import java.io.IOException;
import java.net.*;
import java.util.*;
import java.util.stream.Collectors;

/**
 * IPv4地址验证工具类
 * 功能：验证指定IP是否属于本机，获取本机IP信息、子网信息、广播地址等
 */
public final class IpAddressUtil {

    private static Set<String> localIpAddresses;
    private static Set<String> localSubnets;

    // 私有构造方法防止实例化
    private IpAddressUtil() {
        throw new UnsupportedOperationException("这是一个工具类，不能被实例化");
    }

    static {
        refreshLocalIpAddresses();
    }

    /**
     * 刷新本地IP地址和子网缓存
     */
    public static synchronized void refreshLocalIpAddresses() {
        localIpAddresses = Collections.unmodifiableSet(getAllLocalIpAddresses());
        localSubnets = Collections.unmodifiableSet(calculateLocalSubnets());
    }

    /**
     * 获取本机所有IPv4地址
     * @return 本机所有IPv4地址的不可修改集合
     */
    public static Set<String> getAllLocalIpAddresses() {
        Set<String> ipSet = new LinkedHashSet<>();

        try {
            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
            while (interfaces.hasMoreElements()) {
                NetworkInterface iface = interfaces.nextElement();
                if (iface.isLoopback() || !iface.isUp()) {
                    continue;
                }

                Enumeration<InetAddress> addresses = iface.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    InetAddress addr = addresses.nextElement();
                    // 只添加IPv4地址
                    if (addr instanceof Inet4Address) {
                        ipSet.add(addr.getHostAddress());
                    }
                }
            }
        } catch (SocketException e) {
            throw new RuntimeException("无法获取网络接口信息", e);
        }

        // 包含标准回环地址
        ipSet.add("127.0.0.1");

        return ipSet;
    }

    /**
     * 计算本机所有IPv4子网
     * @return 子网地址集合（格式：x.x.x.x/xx）
     */
    private static Set<String> calculateLocalSubnets() {
        Set<String> subnets = new HashSet<>();

        try {
            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
            while (interfaces.hasMoreElements()) {
                NetworkInterface iface = interfaces.nextElement();
                if (iface.isLoopback() || !iface.isUp()) {
                    continue;
                }

                for (InterfaceAddress ifaceAddr : iface.getInterfaceAddresses()) {
                    InetAddress addr = ifaceAddr.getAddress();
                    // 只处理IPv4地址
                    if (addr instanceof Inet4Address) {
                        short prefixLength = ifaceAddr.getNetworkPrefixLength();
                        // 过滤掉无效的前缀长度
                        if (prefixLength > 0 && prefixLength <= 32) {
                            String subnet = calculateSubnet(addr.getHostAddress(), prefixLength);
                            subnets.add(subnet);
                        }
                    }
                }
            }
        } catch (SocketException e) {
            throw new RuntimeException("无法获取网络接口信息", e);
        }

        return subnets;
    }

    /**
     * 根据IP和前缀长度计算子网地址
     * @param ipAddress IPv4地址
     * @param prefixLength 前缀长度
     * @return 子网地址（格式：x.x.x.x/xx）
     */
    private static String calculateSubnet(String ipAddress, short prefixLength) {
        try {
            byte[] ipBytes = InetAddress.getByName(ipAddress).getAddress();
            byte[] maskBytes = new byte[4];
            int remainingPrefix = prefixLength;

            // 计算子网掩码
            for (int i = 0; i < maskBytes.length; i++) {
                if (remainingPrefix >= 8) {
                    maskBytes[i] = (byte) 0xFF;
                    remainingPrefix -= 8;
                } else if (remainingPrefix > 0) {
                    maskBytes[i] = (byte) (0xFF << (8 - remainingPrefix));
                    remainingPrefix = 0;
                } else {
                    maskBytes[i] = 0;
                }
            }

            // 计算网络地址
            byte[] netBytes = new byte[4];
            for (int i = 0; i < 4; i++) {
                netBytes[i] = (byte) (ipBytes[i] & maskBytes[i]);
            }

            return InetAddress.getByAddress(netBytes).getHostAddress() + "/" + prefixLength;
        } catch (UnknownHostException e) {
            throw new IllegalArgumentException("无效的IPv4地址: " + ipAddress, e);
        }
    }

    /**
     * 根据子网计算广播地址
     * @param subnet 子网地址（格式：x.x.x.x/xx）
     * @return 广播地址
     * @throws IllegalArgumentException 如果子网格式无效
     */
    public static String getBroadcastAddress(String subnet) {
        if (!subnet.contains("/")) {
            throw new IllegalArgumentException("无效的子网格式，必须包含前缀长度");
        }

        String[] parts = subnet.split("/");
        if (parts.length != 2) {
            throw new IllegalArgumentException("无效的子网格式");
        }

        try {
            String ip = parts[0];
            int prefixLength = Integer.parseInt(parts[1]);

            if (prefixLength <= 0 || prefixLength > 32) {
                throw new IllegalArgumentException("前缀长度必须在1-32之间");
            }

            byte[] ipBytes = InetAddress.getByName(ip).getAddress();
            byte[] maskBytes = new byte[4];
            int remainingPrefix = prefixLength;

            // 计算子网掩码
            for (int i = 0; i < maskBytes.length; i++) {
                if (remainingPrefix >= 8) {
                    maskBytes[i] = (byte) 0xFF;
                    remainingPrefix -= 8;
                } else if (remainingPrefix > 0) {
                    maskBytes[i] = (byte) (0xFF << (8 - remainingPrefix));
                    remainingPrefix = 0;
                } else {
                    maskBytes[i] = 0;
                }
            }

            // 计算广播地址（网络地址 | ~子网掩码）
            byte[] broadcastBytes = new byte[4];
            for (int i = 0; i < 4; i++) {
                broadcastBytes[i] = (byte) (ipBytes[i] | ~maskBytes[i]);
            }

            return InetAddress.getByAddress(broadcastBytes).getHostAddress();
        } catch (UnknownHostException e) {
            throw new IllegalArgumentException("无效的IP地址: " + subnet, e);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("无效的前缀长度: " + parts[1], e);
        }
    }

    /**
     * 获取本机所有IPv4子网
     * @return 子网地址数组（格式：x.x.x.x/xx）
     */
    public static String[] getAllLocalSubnets() {
        return localSubnets.toArray(new String[0]);
    }

    /**
     * 检查指定IP是否属于本机
     * @param ipAddress 要检查的IP地址
     * @return 如果是本机IP返回true，否则返回false
     * @throws IllegalArgumentException 如果ipAddress为null或空
     */
    public static boolean isLocalIpAddress(String ipAddress) {
        if (ipAddress == null || ipAddress.trim().isEmpty()) {
            throw new IllegalArgumentException("IP地址不能为null或空");
        }

        try {
            InetAddress addr = InetAddress.getByName(ipAddress.trim());
            // 只比较IPv4地址
            if (addr instanceof Inet4Address) {
                ipAddress = addr.getHostAddress();
                return localIpAddresses.contains(ipAddress);
            }
            return false;
        } catch (UnknownHostException e) {
            return false;
        }
    }

    /**
     * 获取本机第一个非回环IPv4地址
     * @return IPv4地址字符串，如果没有则返回null
     */
    public static String getPrimaryIPv4Address() {
        return localIpAddresses.stream()
                .filter(ip -> ip.chars().filter(c -> c == '.').count() == 3)
                .filter(ip -> !ip.equals("127.0.0.1"))
                .findFirst()
                .orElse(null);
    }

    /**
     * 获取格式化后的本机IP地址列表
     * @return 逗号分隔的IP地址字符串
     */
    public static String getFormattedIpAddresses() {
        return localIpAddresses.stream()
                .sorted()
                .collect(Collectors.joining(", "));
    }

    /**
     * 获取本机主机名
     * @return 主机名
     */
    public static String getHostname() {
        try {
            return InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException e) {
            return "unknown";
        }
    }

    /**
     * 根据子网获取本机对应的IP地址（通过广播方式）
     * @param subnet 子网地址（格式：x.x.x.x/xx）
     * @return 本机在该子网中的IP地址，如果没有匹配则返回null
     */
    public static String getLocalIpBySubnet(String subnet) {
        if (!subnet.contains("/")) {
            throw new IllegalArgumentException("无效的子网格式，必须包含前缀长度");
        }

        String[] parts = subnet.split("/");
        if (parts.length != 2) {
            throw new IllegalArgumentException("无效的子网格式");
        }

        try {
            // 计算子网广播地址
            String broadcastAddress = getBroadcastAddress(subnet);

            // 使用随机端口避免冲突
            int port = getAvailablePort();

            try (DatagramSocket socket = new DatagramSocket(port)) {
                // 设置超时防止永久阻塞
                socket.setSoTimeout(1000);
                socket.setBroadcast(true);

                // 创建广播数据包
                byte[] buffer = new byte[0];
                InetAddress bAddr = InetAddress.getByName(broadcastAddress);
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length, bAddr, port);

                // 启动接收线程
                ResponseCapturer capturer = new ResponseCapturer(socket);
                capturer.start();

                // 发送广播包
                socket.send(packet);

                // 等待响应并获取IP
                String ip = capturer.getCapturedIp();
                if (ip != null) {
                    // 验证IP是否在子网内
                    if (isIpInSubnet(ip, subnet)) {
                        return ip;
                    }
                }
                return null;
            }
        } catch (Exception e) {
            throw new IllegalArgumentException("无法通过广播确定IP: " + e.getMessage());
        }
    }

    /**
     * 验证IP是否属于指定子网
     * @param ip 待验证的IP地址
     * @param subnet 子网地址（格式：x.x.x.x/xx）
     * @return 是否属于子网
     */
    private static boolean isIpInSubnet(String ip, String subnet) {
        try {
            String[] parts = subnet.split("/");
            InetAddress subnetAddr = InetAddress.getByName(parts[0]);
            int prefix = Integer.parseInt(parts[1]);

            byte[] subnetBytes = subnetAddr.getAddress();
            byte[] ipBytes = InetAddress.getByName(ip).getAddress();
            byte[] mask = calculateSubnetMask(prefix);

            for (int i = 0; i < 4; i++) {
                if ((subnetBytes[i] & mask[i]) != (ipBytes[i] & mask[i])) {
                    return false;
                }
            }
            return true;
        } catch (Exception e) {
            return false;
        }
    }


    /**
     * 辅助方法：根据前缀长度计算子网掩码
     * @param prefixLength 子网前缀长度（1-32）
     * @return 子网掩码的字节数组
     */
    private static byte[] calculateSubnetMask(int prefixLength) {
        byte[] mask = new byte[4];
        int remainingBits = prefixLength;

        for (int i = 0; i < mask.length; i++) {
            if (remainingBits >= 8) {
                mask[i] = (byte) 0xFF;
                remainingBits -= 8;
            } else if (remainingBits > 0) {
                mask[i] = (byte) (0xFF << (8 - remainingBits));
                remainingBits = 0;
            } else {
                mask[i] = 0;
            }
        }

        return mask;
    }

    /**
     * 内部类：捕获自身广播响应的线程
     */
    private static class ResponseCapturer extends Thread {
        private final DatagramSocket socket;
        private String capturedIp;
        private final Object lock = new Object();

        ResponseCapturer(DatagramSocket socket) {
            this.socket = socket;
            this.capturedIp = null;
        }

        @Override
        public void run() {
            try {
                byte[] buffer = new byte[1024];
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                socket.receive(packet);
                capturedIp = packet.getAddress().getHostAddress();
            } catch (SocketTimeoutException ignored) {
                // 超时是正常情况，无需处理
            } catch (Exception e) {
                // 捕获其他异常但不中断主流程
            }
        }

        String getCapturedIp() throws InterruptedException {
            // 等待线程完成或超时
            this.join(1500);
            return capturedIp;
        }
    }

    /**
     * 获取一个本机当前未被占用的可用端口号
     *
     * @return 可用端口号，范围在 0~65535 之间
     * @throws RuntimeException 如果无法找到可用端口
     */
    public static int getAvailablePort() {
        try (ServerSocket socket = new ServerSocket(0)) {
            return socket.getLocalPort();
        } catch (IOException e) {
            throw new RuntimeException("无法获取可用端口", e);
        }
    }
}